//
//  PSEvaluator.m
//  PrefCore
//
//  Created by Sergey on 5/30/14.
//  Copyright (c) 2014 Play-on-smart. All rights reserved.
//

#import "PSEvaluator.h"
#import "PSFilter.h"

@interface PSMinEvaluator : PSEvaluator

-(PSCardHash)startWithCandidates:(PSHandHash)candidates;

-(PSCardHash)nextWithCandidates:(PSHandHash)candidates andCurrent:(PSCardHash)current;

-(PSHandHash)moveCandidatesWithHands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer;

-(PSHandHash)filterCandidates:(PSHandHash)candidates
    hands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer
    open:(BOOL)open;

@end

@interface PSMisereEvaluator : PSMinEvaluator

-(BOOL)isBetterChild:(PSPackedScore)childScore
    parent:(PSPackedScore)parentScore
    player:(PSPlayerId)player
    declarer:(PSPlayerId)declarer;

-(PSHandHash)filterCandidates:(PSHandHash)candidates
    hands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer
    open:(BOOL)open;

@end

@interface PSPassingEvaluator : PSMinEvaluator

-(BOOL)isBetterChild:(PSPackedScore)childScore
    parent:(PSPackedScore)parentScore
    player:(PSPlayerId)player
    declarer:(PSPlayerId)declarer;

-(PSHandHash)filterCandidates:(PSHandHash)candidates
    hands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer
    open:(BOOL)open;

-(PSHandHash)moveCandidatesWithHands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer
    drop:(PSHandHash)drop
    open:(BOOL)open;

@end

@interface PSTrickingEvaluator : PSEvaluator

-(PSCardHash)startWithCandidates:(PSHandHash)candidates;

-(PSCardHash)nextWithCandidates:(PSHandHash)candidates andCurrent:(PSCardHash)current;

-(PSHandHash)moveCandidatesWithHands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer;

-(BOOL)isBetterChild:(PSPackedScore)childScore
    parent:(PSPackedScore)parentScore
    player:(PSPlayerId)player
    declarer:(PSPlayerId)declarer;

-(PSHandHash)filterCandidates:(PSHandHash)candidates
    hands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer
    open:(BOOL)open;

@end

@implementation PSMinEvaluator

-(PSCardHash)startWithCandidates:(PSHandHash)candidates {
    return PSHandGetFirstCard(candidates);
}

-(PSCardHash)nextWithCandidates:(PSHandHash)candidates andCurrent:(PSCardHash)current {
    return PSHandGetNextCard(candidates, current);
}

-(PSHandHash)moveCandidatesWithHands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer
{
    int in = hands[player];
    PSHandHash out = 0;
    PSCardSuit suit = PSTrickCardSuit(trick);
    // trick has no defined suit yet - return the whole hand?
    if (suit == kSuitNone) {
        return in;
    }
    // check the set matching the trick suit
    PSCardRankSet set = PSHandGetSuit(in, suit);
    if (set) {
        // include only:
        // 1. the highest card that is lower then trick's highest card
        // 2. all the cards higher then trick's highest card
        PSCard *highest = PSTrickHighestCard(trick, trump);
        if (!highest) {
            // no highest card yet, add the whole set
            return PSHandKeepSuit(in, suit);
        }
        PSCardRank lower = PSCardSetNextLowerRank(set, highest.rank);
        // add the card lower then the trick highest
        if (lower != kRankNull) {
            out = PSHandAddCard(0, [PSCard cardWithSuit:suit andRank:lower]);
        }
        // add higher cards
        for (PSCardRank higher = PSCardSetNextHigherRank(set, highest.rank); higher ; higher = PSCardSetNextHigherRank(set, higher)) {
            out = PSHandAddCard(out, [PSCard cardWithSuit:suit andRank:higher]);
        }
    } else {
        // no cards in trick's suit in this hand - for all other suits add their highest cards only
        for (int i = 0 ; i != PS_CARD_SUIT_RISING_LEN ; i++) {
            PSCardSuit s = PSCardSuitRising[i];
            if (s != suit) {
                PSCardRank highest = PSCardSetHighestRank(PSHandGetSuit(in, s));
                if (highest != kRankNull) {
                    out = PSHandAddCard(out, [PSCard cardWithSuit:s andRank:highest]);
                }
            }
        }
    }
    NSAssert(out != 0, @"Empty candidate list.");
    return out;
}

-(PSHandHash)filterCandidates:(PSHandHash)candidates
    hands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer
    open:(BOOL)open
{
    return PSFilterSameSuitConsecutiveCardsMax(candidates, PSHandRemaining(hands));
}

@end

@implementation PSMisereEvaluator

-(BOOL)isBetterChild:(PSPackedScore)childScore
    parent:(PSPackedScore)parentScore
    player:(PSPlayerId)player
    declarer:(PSPlayerId)declarer
{
    return player == declarer
    ?   PSScoreGet(childScore, player) < PSScoreGet(parentScore, player)
    :   PSScoreGet(childScore, declarer) > PSScoreGet(parentScore, declarer);
}

-(PSHandHash)filterCandidates:(PSHandHash)candidates
    hands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer
    open:(BOOL)open
{
    int filtered = candidates;
    if (declarer == 0) {
        if (player != declarer) {
            filtered = PSFilterHumanPlaysMisere(self, filtered, player, trick, round, handIndex, hands);
        }
    } else {
        if (player == declarer) {
            filtered = PSFilterComputerPlaysMisere(filtered, trick);
        }
    }
    if (filtered == 0) {
        filtered = candidates;
    }
    // The call to [super filterCandidates:] has to be made after the call to [FilterComputerPlaysMisere apply]
    // in order to allow the FilterComputerPlaysMisere to see the original candidates.
    return [super filterCandidates:filtered
        hands:hands
        player:player
        trick:trick
        trump:trump
        round:round
        handIndex:handIndex
        declarer:declarer
        open:open];
}

@end

@implementation PSPassingEvaluator

-(BOOL)isBetterChild:(PSPackedScore)childScore
    parent:(PSPackedScore)parentScore
    player:(PSPlayerId)player
    declarer:(PSPlayerId)declarer
{
    return PSScoreGet(childScore, player) < PSScoreGet(parentScore, player);
}

-(PSHandHash)filterCandidates:(PSHandHash)candidates
    hands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer
    open:(BOOL)open
{
    PSHandHash filtered = PSFilterMissingSuits(candidates, hands, player);
    return [super filterCandidates:filtered
        hands:hands
        player:player
        trick:trick
        trump:trump
        round:round
        handIndex:handIndex
        declarer:declarer
        open:open];
}

-(PSHandHash)moveCandidatesWithHands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer
    drop:(PSHandHash)drop
    open:(BOOL)open
{
    PSHandHash candidates = [self moveCandidatesWithHands:hands
        player:player
        trick:trick
        trump:trump
        round:round
        handIndex:handIndex
        declarer:declarer];
    return [self filterCandidates:candidates
        hands:hands
        player:player
        trick:trick
        trump:trump
        round:round
        handIndex:handIndex
        declarer:declarer
        open:open];
}

@end

@implementation PSTrickingEvaluator

-(PSCardHash)startWithCandidates:(PSHandHash)candidates {
    return PSHandGetLastCard(candidates);
}

-(PSCardHash)nextWithCandidates:(PSHandHash)candidates andCurrent:(PSCardHash)current {
    return PSHandGetPreviousCard(candidates, current);
}

-(PSHandHash)moveCandidatesWithHands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer
{
    PSHandHash in = hands[player];
    PSHandHash out = 0;
    
    // No cards in the trick - return the whole hand, unless it's the first hand in the first round
    if (handIndex == 0) {
        // Cannot optimize yet for advanced round or declarer, in this case we return whole hand
        if (round > 2 || player == declarer) {
            return in;
        }
        // In the first few rounds we can use the "Rules of steel":
        // 1. Move towards whister with highest card
        // 2. Move towards declarer with the lowest card
        if (PSGameNextPlayerId(player) == declarer) {
            for (int i = 0 ; i != PS_CARD_SUIT_RISING_LEN ; i++) {
                PSCardSuit s = PSCardSuitRising[i];
                PSCardRank lowest = PSCardSetLowestRank(PSHandGetSuit(in, s));
                if (lowest != kRankNull) {
                    out = PSHandAddCard(out, [PSCard cardWithSuit:s andRank:lowest]);
                }
            }
        } else {
            for (int i = 0 ; i != PS_CARD_SUIT_RISING_LEN ; i++) {
                PSCardSuit s = PSCardSuitRising[i];
                PSCardRank highest = PSCardSetHighestRank(PSHandGetSuit(in, s));
                if (highest != kRankNull) {
                    out = PSHandAddCard(out, [PSCard cardWithSuit:s andRank:highest]);
                }
            }
        }
        return out;
    }
    // try to get the trick's suit
    PSCardSuit suit = PSTrickCardSuit(trick);
    // try to return our set of cards in this suit
    PSCardRankSet set = PSHandGetSuit(in, suit);
    if (set) {
        if (handIndex == 2 && player == declarer) {
            // for the last hand return the lowest card, higher than the trick's highest card
            PSCard *highest = PSTrickHighestCard(trick, trump);
            if (highest.suit == suit) {
                // add also the lowest card that is higher than the highest trick card
                PSCardRank higher = PSCardSetNextHigherRank(set, highest.rank);
                
                if (higher != kRankNull) {
                    return PSHandAddCard(0, [PSCard cardWithSuit:suit andRank:higher]);
                }
            }
            // always add the lowest card in the trick's suit
            return PSHandAddCard(out, [PSCard cardWithSuit:suit andRank:PSCardSetLowestRank(set)]);
        }
        // no optimization yet, add the whole suit
        return PSHandKeepSuit(in, suit);
    } else {
        // no cards in trick's suit, try trumps
        // declarer move optimization: can play the lowest trump (if has one) when sits on last hand or
        // the last hand has cards in trick suit
        if (trump != kSuitNone && (set = PSHandKeepSuit(in, trump)) != 0) {
            if (player == declarer && (handIndex == 2 || PSHandKeepSuit(hands[PSGameNextPlayerId(player)], suit) != 0)) {
                PSCardRankSet s = PSHandGetSuit(in, trump);
                // the highest card in the trick may be another trump card, so we have to beat it with the higher trump
                // should we have one otherwise play the lowest trump
                PSCard *highest = PSTrickHighestCard(trick, trump);
                PSCardRank higher = highest.suit == trump ? PSCardSetNextHigherRank(s, highest.rank) : kRankNull;
                
                NSLog(
                    @"trick: %@, highest: %@ higher: %@%@"
                ,   PSTrickDescription(trick, trump)
                ,   highest
                ,   PSCardSuitDescription(trump)
                ,   PSCardRankDescription(higher)
                );
                return PSHandAddCard(
                    0
                ,   [PSCard cardWithSuit:trump andRank:higher == kRankNull ? PSCardSetLowestRank(s) : higher]
                );
            }
            return set;
        }
        // the hand has no trumps as well - add the lowest cards in all remaining suits
        for (int i = 0 ; i != PS_CARD_SUIT_RISING_LEN ; i++) {
            PSCardSuit s = PSCardSuitRising[i];
            if (s != suit) {
                PSCardRank lowest = PSCardSetLowestRank(PSHandGetSuit(in, s));
                if (lowest != kRankNull) {
                    out = PSHandAddCard(out, [PSCard cardWithSuit:s andRank:lowest]);
                }
            }
        }
    }
    NSLog(
        @"X#%u:[%@], trick: [%@], suit: %@, optimized: %@"
    ,   handIndex
    ,   PSHandDescription(in)
    ,   PSTrickDescription(trick, trump)
    ,   PSCardSuitDescription(PSTrickCardSuit(trick))
    ,   PSHandDescription(out)
    );
    NSAssert(out != 0, @"Empty candidates list");
    return out;
}

-(BOOL)isBetterChild:(PSPackedScore)childScore
    parent:(PSPackedScore)parentScore
    player:(PSPlayerId)player
    declarer:(PSPlayerId)declarer {
    return player == declarer
    ?   PSScoreGet(childScore, player) > PSScoreGet(parentScore, player)
    :   PSScoreGet(childScore, declarer) < PSScoreGet(parentScore, declarer);
}

-(PSHandHash)filterCandidates:(PSHandHash)candidates
    hands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer
    open:(BOOL)open
{
    PSHandHash filtered = candidates;
    if (player == declarer && trump != kSuitNone && handIndex == 0 && round < 5) {
        filtered = open
        ?   PSFilterDeclarerFirstHandMoveOpen(candidates, trump, declarer, hands)
        :   PSFilterDeclarerFirstHandMove(candidates, trump, declarer, hands);
    }
    if (player != declarer) {
        if (handIndex == 0) {
            if (round < 4) {
                filtered = PSFilterSecondPlusKingTrick(filtered, player, hands[player], declarer, hands[declarer]);
                filtered = PSFilterThirdQueenTrick(filtered, hands[declarer]);
                filtered = PSFilterWhisterFirstHandMove(filtered, hands[player], hands[declarer]);
            }
        } else if (round < 9 && ((handIndex == 1 && PSGamePreviousPlayerId(player) == declarer) || handIndex == 2)) {
            if (open) {
                filtered = PSFilterWhisterAfterDeclarerMoveOpen(self, filtered, hands, player, trick, trump, round, handIndex, declarer);
            } else {
                filtered = PSFilterWhisterAfterDeclarerMove(self, filtered, player, trick, trump, handIndex, declarer);
            }
        }
    }
    if (handIndex != 0 && round < 8) {
        filtered = PSFilterPlayerNonFirstHandMove(filtered, player, trick, trump, handIndex);
    }
    return PSFilterSameSuitConsecutiveCardsMin(filtered, PSHandRemaining(hands));
}

@end

@implementation PSEvaluator

+(PSEvaluator*)evaluatorForType:(PSEvaluatorType)type {
    static PSEvaluator *misere, *passing, *tricking;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        misere = [[PSMisereEvaluator alloc] init];
        passing = [[PSPassingEvaluator alloc] init];
        tricking = [[PSTrickingEvaluator alloc] init];
    });
    switch (type) {
        case kEvaluatorMisere: return misere;
        case kEvaluatorPassing: return passing;
        case kEvaluatorTricking: return tricking;
    }
    NSAssert(NO, @"Invalid evaluator type is specified.");
    return nil;
}

-(PSCardHash)startWithCandidates:(PSHandHash)candidates {
    NSAssert(NO, @"Subclass must override this method");
    return 0;
}

-(PSCardHash)nextWithCandidates:(PSHandHash)candidates andCurrent:(PSCardHash)current {
    NSAssert(NO, @"Subclass must override this method");
    return 0;
}

-(PSHandHash)moveCandidatesWithHands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer
{
    NSAssert(NO, @"Subclass must override this method");
    return 0;
}

-(PSHandHash)moveCandidatesWithHands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer
    drop:(PSHandHash)drop
    open:(BOOL)open
{
    PSHandHash res = [self moveCandidatesWithHands:hands
        player:player
        trick:trick
        trump:trump
        round:round
        handIndex:handIndex
        declarer:declarer];
    
    res = [self filterCandidates:res
        hands:hands
        player:player
        trick:trick
        trump:trump
        round:round
        handIndex:handIndex
        declarer:declarer open:open];
    /*
    // for declarer move we have to check if its candidates are actually dropped cards
    // as in this case we have to remove the dropped cards from its hand and get move candidates again
    if (player == declarer && (res | drop) == drop) {
        PSHandHash tempHands[3];
        memcpy(tempHands, hands, sizeof(tempHands));
        // Remove dropped cards from the temporary declarer hand
        tempHands[declarer] = PSHandRemoveHand(tempHands[declarer], drop);
        PSHandHash tempCandidates = [self moveCandidatesWithHands:tempHands
            player:player
            trick:trick
            trump:trump
            round:round
            handIndex:handIndex
            declarer:declarer];
        res |= [self filterCandidates:tempCandidates
            hands:tempHands
            player:player
            trick:trick
            trump:trump
            round:round
            handIndex:handIndex
            declarer:declarer
            open:open];
    }
    */
    return res;
}

-(BOOL)isBetterChild:(PSPackedScore)childScore
              parent:(PSPackedScore)parentScore
              player:(PSPlayerId)player
            declarer:(PSPlayerId)declarer {
    NSAssert(NO, @"Subclass must override this method");
    return NO;
}

-(PSHandHash)filterCandidates:(PSHandHash)candidates
    hands:(PSHandHash*)hands
    player:(PSPlayerId)player
    trick:(PSTrickHash)trick
    trump:(PSCardSuit)trump
    round:(uint32_t)round
    handIndex:(uint8_t)handIndex
    declarer:(PSPlayerId)declarer
    open:(BOOL)open
{
    PSHandHash res = candidates;
    if (player == declarer && trump != kSuitNone && handIndex == 0 && round < 5) {
        res = open
        ? PSFilterDeclarerFirstHandMoveOpen(candidates, trump, declarer, hands)
        : PSFilterDeclarerFirstHandMove(candidates, trump, declarer, hands);
    }
    if (player != declarer) {
        if (handIndex == 0) {
            if (round < 4) {
                res = PSFilterSecondPlusKingTrick(res, player, hands[player], declarer, hands[declarer]);
                res = PSFilterThirdQueenTrick(res, hands[declarer]);
                res = PSFilterWhisterFirstHandMove(res, hands[player], hands[declarer]);
            }
        } else if (round < 9 && ((handIndex == 1 && PSGamePreviousPlayerId(player) == declarer) || handIndex == 2)) {
            if (open) {
                res = PSFilterWhisterAfterDeclarerMoveOpen(self, res, hands, player, trick, trump, round, handIndex, declarer);
            } else {
                res = PSFilterWhisterAfterDeclarerMove(self, res, player, trick, trump, handIndex, declarer);
            }
        }
    }
    if (handIndex != 0 && round < 8) {
        res = PSFilterPlayerNonFirstHandMove(res, player, trick, trump, handIndex);
    }
    return PSFilterSameSuitConsecutiveCardsMin(res, PSHandRemaining(hands));
}

@end
